﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using PowerGui.AddOn.Interfaces;
using Quest.PowerGUI;
using Quest.PowerGUI.Engine.Shell;
using Quest.PowerGUI.Engine.Shell.Host;
using Quest.PowerGUI.ScriptEditor.Shared;
using Quest.PowerGUI.ScriptEditor.Shared.Debugger;

namespace PowerGui.AddOn.Impl
{
    [Export(typeof (IDebugger))]
    [Export(typeof (PowerShellDebugger))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class PowerShellDebugger : IDebuggerUserSettings, IDebugger
    {
        private DebuggerManager _manager;
        private AutoResetEvent _debugWait;
        private static Object locker = new object();

        public PowerShellDebugger()
        {
            _debugWait = new AutoResetEvent(true);
            IsIgnoreSignature = true;
            UseBreaking = true;
        }

        public object InternalDebugger
        {
            get
            {
                return Debugger;
            }
        }

        public IDebuggerManager Debugger
        {
            get
            {
                if (_manager == null)
                {
                    _manager = new DebuggerManager(ConfigLoader.SettingsManager, new PowerShellDebugger());
                    DebuggerManager.PSHost.DebuggerManager = _manager;

                    _manager.ScriptCompleted += UpdateSettings;

                    _manager.ScriptCompleted += UpdateSettings;
                    _manager.ScriptFailed += ManagerScriptFailed;
                    _manager.ScriptSuspended += ManagerScriptSuspended;
                    _manager.ScriptResumed += ManagerScriptResumed;
                    _manager.ScriptStarted += ManagerScriptStarted;
                    _manager.HostCallBack += HostCallback;
                }
                return _manager;
            }
        }

        #region IDebugger Members

        public IScriptEditor CurrentEditor
        {
            get { return Debugger.CurrentPSControl as PsControlWrapper; }
            set { Debugger.CurrentPSControl = value as PsControlWrapper; }
        }

        [Import]
        internal ConfigLoader ConfigLoader { get; set; }

        public void SetVariable(string name, object obj)
        {
            _debugWait.WaitOne();
            Debugger.Shell.Runspace.SessionStateProxy.SetVariable(name, obj);
            UpdateSettings(null, null);
        }

        #endregion

        #region Implementation of IDebuggerUserSettings

        public bool DebugInNewRunspace { get; set; }

        public bool IsIgnoreSignature { get; set; }

        public bool LoadProfiles { get; set; }

        public bool STAMode { get; set; }

        public bool UseBreaking { get; set; }

        #endregion

        private void ManagerScriptStarted(object sender, EventArgs e)
        {
            OnScriptStarted();
        }

        private void ManagerScriptResumed(object sender, TypedEventArgs<SEPosition> e)
        {
            OnScriptResumed();
        }

        private void ManagerScriptSuspended(object sender, TypedEventArgs<SEPosition> e)
        {
            OnScriptSuspended(new ScriptPosition(e.Value));
        }

        private void ManagerScriptFailed(object sender, PipelineStateInfoEventArgs e)
        {
            OnScriptFailed(new PipelineStateInfo(e));
        }

        public void UpdateSettings(object sender, EventArgs e)
        {
            if (Debugger.Shell != null)
            {
                var param = new ScriptParameters
                                {
                                    NewThread = false,
                                    Nested = false
                                };
                ICollection<SnapinInfo> modulesSnapins = Debugger.Shell.GetModulesSnapins(false, param);

                lock (locker)
                {
                    var pvsxModle =
                        modulesSnapins.FirstOrDefault(
                            m => m.Name.Equals("PowerGuiVsx.Module", StringComparison.InvariantCultureIgnoreCase));

                    if (pvsxModle != null)
                    {
                        modulesSnapins.Remove(pvsxModle);
                    }

                    ConfigUtils.PutLoadedSnapins(SettingsManager.ConsoleSettings.Settings, modulesSnapins);
                    ConfigUtils.PutAdditionalSnapins(SettingsManager.ConsoleSettings.Settings, from x in modulesSnapins
                                                                                               where
                                                                                                   x.Type ==
                                                                                                   SnapinType.Module
                                                                                               select x);
                    SettingsManager.ConsoleSettings.Settings.SavePart(SettingsManager.ID_SHELLPART);
                    CmdLetImporter.ImportCommands(Debugger.Shell, SettingsManager.ConsoleSettings.Settings);
                    PSSyntaxEditorControl.InitializeProviderISense(Debugger.Shell, Debugger);
                }
            }

            OnScriptCompleted();
        }

        private void HostCallback(object sender, HostCallbackParameter param)
        {
            switch (param.Type)
            {
                case HostCallbackParameterTypeEnum.Write:
                    HostWriteCallback((HostWriteCallbackParameter) param);
                    return;
                case HostCallbackParameterTypeEnum.ClearOutput:
                    //CleanOutputWindow();
                    return;
                case HostCallbackParameterTypeEnum.Prompt:
                    // this.ParameterTypeCallback((HostPromtParameter)param);
                    return;
                case HostCallbackParameterTypeEnum.PromptForCredential:
                    //this.PromptForCredentialCallback((HostPromptForCredentialParameter)param);
                    return;
                case HostCallbackParameterTypeEnum.PromptForChoice:
                    //this.ChoiceTypeCallback((HostPromtForChoiceParameter)param);
                    return;
            }
        }

        private void HostWriteCallback(HostWriteCallbackParameter param)
        {
            if (param == null)
            {
                throw new ArgumentNullException();
            }
            switch (param.WriteType)
            {
                case HostWriteTypeEnum.Write:
                case HostWriteTypeEnum.WriteLine:
                    OnWrite(param.Message);
                    return;

                case HostWriteTypeEnum.WriteWithParams:
                case HostWriteTypeEnum.WriteLineWithParams:
                    OnWrite(param.Message);
                    return;

                case HostWriteTypeEnum.WriteDebugLine:
                    OnWriteDebug(param.Message);
                    return;

                case HostWriteTypeEnum.WriteErrorLine:
                    OnWriteError(param.Message);
                    return;

                case HostWriteTypeEnum.WriteVerboseLine:
                    OnWriteVerbose(param.Message);
                    return;

                case HostWriteTypeEnum.WriteWarningLine:
                    OnWriteWarning(param.Message);
                    return;

                case HostWriteTypeEnum.WriteProgress:
                    var p = new ProgressRecord(param.Record.ActivityId, param.Record.Activity,
                                               param.Record.StatusDescription);
                    OnWriteProgress(p);
                    return;
            }
        }

        #region Implementation of IDebugger


        public void Execute()
        {
            _debugWait.WaitOne();
            var sp = new ScriptProvider {ScriptName = Debugger.CurrentPSControl.Filename};

            Debugger.Execute(sp, RunScriptMode.RunDebug);
        }

        public void ExecuteNoDebug()
        {
            _debugWait.WaitOne();
            var sp = new ScriptProvider { ScriptName = Debugger.CurrentPSControl.Filename };

            Debugger.Execute(sp, RunScriptMode.Run);
        }

        public void Execute(string fileName)
        {
            _debugWait.WaitOne();
            var sp = new ScriptProvider {ScriptName = fileName};

            Debugger.Execute(sp, RunScriptMode.RunDebug);
        }

        public void ExecuteText(string script)
        {
            _debugWait.WaitOne();
            var sp = new ScriptProvider {Script = script};

            Debugger.Execute(sp, RunScriptMode.RunDebug);
        }

        public void Break()
        {
            if (Debugger.DebugEnabled && Debugger.State == ShellExecutingState.Running)
            {
                Debugger.BreakDebug();
            }
        }

        public void Stop()
        {
            if (Debugger.State == ShellExecutingState.Running)
            {
                Debugger.StopDebug();
            }
        }

        public event EventHandler ScriptStarted;
        public event EventHandler ScriptCompleted;
        public event EventHandler<EventArgs<IPipelineStateInfo>> ScriptFailed;
        public event EventHandler<EventArgs<IScriptPosition>> ScriptSuspended;
        public event EventHandler ScriptResumed;
        public event EventHandler<EventArgs<IScriptEditor, bool>> ActivateEditor;
        public event EventHandler<EventArgs<string>> Write;
        public event EventHandler<EventArgs<string>> WriteWarning;
        public event EventHandler<EventArgs<string>> WriteError;
        public event EventHandler<EventArgs<string>> WriteVerbose;
        public event EventHandler<EventArgs<string>> WriteDebug;
        public event EventHandler<EventArgs<ProgressRecord>> WriteProgress;


        public Runspace Runspace
        {
            get { return Debugger.Shell.Runspace; }
        }


        private void OnScriptStarted()
        {
            if (ScriptStarted != null)
            {
                ScriptStarted(this, new EventArgs());
            }
        }

        private void OnScriptCompleted()
        {
            _debugWait.Set();
            if (ScriptCompleted != null)
            {
                ScriptCompleted(this, new EventArgs());
            }
        }

        private void OnScriptResumed()
        {
            if (ScriptResumed != null)
            {
                ScriptResumed(this, new EventArgs());
            }
        }

        private void OnActivateEditor(IScriptEditor editor, bool isNew)
        {
            if (ActivateEditor != null)
            {
                ActivateEditor(this, new EventArgs<IScriptEditor, bool>(editor, isNew));
            }
        }

        private void OnWrite(string message)
        {
            if (Write != null)
            {
                Write(this, new EventArgs<string>(message));
            }
        }

        private void OnWriteWarning(string message)
        {
            if (WriteWarning != null)
            {
                WriteWarning(this, new EventArgs<string>(message));
            }
        }

        private void OnWriteVerbose(string message)
        {
            if (WriteVerbose != null)
            {
                WriteVerbose(this, new EventArgs<string>(message));
            }
        }

        private void OnWriteDebug(string message)
        {
            if (WriteDebug != null)
            {
                WriteDebug(this, new EventArgs<string>(message));
            }
        }

        private void OnWriteError(string message)
        {
            if (WriteError != null)
            {
                WriteError(this, new EventArgs<string>(message));
            }
        }

        private void OnWriteProgress(ProgressRecord message)
        {
            if (WriteProgress != null)
            {
                WriteProgress(this, new EventArgs<ProgressRecord>(message));
            }
        }

        private void OnScriptFailed(IPipelineStateInfo stateInfo)
        {
            if (ScriptFailed != null)
            {
                ScriptFailed(this, new EventArgs<IPipelineStateInfo>(stateInfo));
            }
        }

        private void OnScriptSuspended(IScriptPosition position)
        {
            if (ScriptSuspended != null)
            {
                ScriptSuspended(this, new EventArgs<IScriptPosition>(position));
            }
        }

        #endregion
    }
}